package com.xhc.lock;

import org.apache.commons.lang3.StringUtils;
import org.apache.zookeeper.*;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

public class MyZookeeperLock implements Lock {

    /** ZooKeeper客户端 */
    private ZooKeeper zooKeeper;

    /** 锁对应的redis中的key */
    private String lockName;

    /** 锁路径的根目录 */
    private static final String lockRoot = "/locks";

    /** 当前节点路径 */
    private String currentNode;

    /** 重入次数记录 */
    public static final ThreadLocal<Integer> reEntry=new ThreadLocal<>();

    public MyZookeeperLock(ZooKeeper zooKeeper, String lockName) {
        this.zooKeeper = zooKeeper;
        this.lockName = lockName;
        try {
            if (zooKeeper.exists(lockRoot, false) == null){
                zooKeeper.create(lockRoot, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {}

    @Override
    public boolean tryLock() {
        // 重入相关判断
        Integer cnt = reEntry.get();
        if (cnt != null && cnt>0){
            reEntry.set(cnt + 1);
            return true;
        }
        try {
            // 创建临时有序节点
            this.currentNode = this.zooKeeper.create(lockRoot + "/" + lockName + "-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            // 获得前置节点
            String preNode = this.getPreNode();
            // 无前置节点则代表获取锁成功，有前置节点，则监听前置节点，当前置节点消失时，当前节点获取锁成功
            if (preNode != null){
                CountDownLatch latch = new CountDownLatch(1);
                if (this.zooKeeper.exists(lockRoot + "/" + preNode, new Watcher() {
                    @Override
                    public void process(WatchedEvent watchedEvent) {
                        latch.countDown();
                    }
                }) == null){
                    reEntry.set(1);
                    return true;
                }
                latch.await();
            }
            reEntry.set(1);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        try {
            // 先减一次重入次数
            reEntry.set(reEntry.get() - 1);
            if (reEntry.get() == 0){
                // 删除当前节点
                this.zooKeeper.delete(this.currentNode, -1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }


    /**
     * 获取当前节点的前置节点
     * @return
     */
    private String getPreNode(){
        try {
            // 获取锁根路径下的所有临时有序节点
            List<String> allLocks = this.zooKeeper.getChildren(lockRoot, false);
            if (CollectionUtils.isEmpty(allLocks)){
                throw new IllegalMonitorStateException("ERROR");
            }
            // 筛选出与本锁相关的节点
            List<String> nodes = allLocks.stream().filter(l -> StringUtils.startsWith(l, lockName + "-")).collect(Collectors.toList());
            // 对锁节点排序
            Collections.sort(nodes);
            // 查找当前节点在排序后列表中的索引，当索引大于零时，说明有前置节点，返回该前置节点；当索引值等于零，说明当前节点没有前置节点，返回null
            String currentNodeName = StringUtils.substringAfterLast(currentNode, "/");
            int i = Collections.binarySearch(nodes, currentNodeName);
            if (i<0){
                throw new IllegalMonitorStateException("ERROR");
            }else if (i>0){
                return nodes.get(i-1);
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalMonitorStateException("ERROR");
        }
    }
}
